﻿using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using AForge.Video.FFMPEG;

using Point = System.Drawing.Point;

namespace AdvanceScreening
{
    public class Capture
    {
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool GetWindowRect(HandleRef hWnd, out RECT lpRect);

        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }

        private readonly ConcurrentQueue<Frame> _frames = new ConcurrentQueue<Frame>();

        public async Task Start(string filename, IntPtr handle, CancellationToken cancellationToken)
        {
            var processFramesCancellationTokenSource = new CancellationTokenSource();

            var captureArea = GetBounds(handle);
            var initialCaptureArea = captureArea;

            var captureTask = Task.Run(() =>
            {
                using (var bitmap = new Bitmap(captureArea.Width, captureArea.Height))
                {
                    using (Graphics g = Graphics.FromImage(bitmap))
                    {
                        var stopwatch = new Stopwatch();
                        stopwatch.Start();

                        while (!cancellationToken.IsCancellationRequested)
                        {
                            /* Update in case source window moves */
                            if (handle != IntPtr.Zero)
                                captureArea = GetBounds(handle);

                            var sourcePoint = new Point(captureArea.Left, captureArea.Top);

                            g.CopyFromScreen(sourcePoint, Point.Empty, captureArea.Size);
                            _frames.Enqueue(new Frame(stopwatch.Elapsed, (Bitmap) bitmap.Clone()));
                        }

                        processFramesCancellationTokenSource.Cancel();
                    }
                }
            }, cancellationToken);

            var processTask = Task.Run(() =>
                ProcessFrames(filename, initialCaptureArea.Width, initialCaptureArea.Height,
                    processFramesCancellationTokenSource.Token),
                processFramesCancellationTokenSource.Token);

            await Task.WhenAll(captureTask, processTask);
        }

        private void ProcessFrames(string filename, int width, int height, CancellationToken cancellationToken)
        {
            using (var writer = new VideoFileWriter())
            {
                writer.Open(filename, width, height, 25, VideoCodec.WMV2, 3000000);
                while (!cancellationToken.IsCancellationRequested)
                {
                    Frame frame;
                    _frames.TryDequeue(out frame);

                    if (frame != null)
                    {
                        try
                        {
                            writer.WriteVideoFrame(frame.Bitmap, frame.Time);
                        }
                        finally
                        {
                            frame.Bitmap.Dispose();
                        }
                    }
                }
            }
        }

        private Rectangle GetBounds(IntPtr handle)
        {
            if (handle != IntPtr.Zero)
            {
                RECT rect;

                if (GetWindowRect(new HandleRef(this, handle), out rect))
                {
                    if (rect.Left < 0)
                    {
                        rect.Right += rect.Left;
                        rect.Left = 0;
                    }

                    if (rect.Top < 0)
                    {
                        rect.Bottom += rect.Top;
                        rect.Top = 0;
                    }

                    var width = rect.Right - rect.Left;
                    var height = rect.Bottom - rect.Top;
                    var adjustedWidth = width + width%2;
                    var adjustedHeight = height + height%2;
                    return new Rectangle(rect.Left, rect.Top, adjustedWidth, adjustedHeight);
                }
            }

            return Screen.GetBounds(Point.Empty);
        }
    }
}
